package com.logisticsrepair.controller;

import cn.dev33.satoken.stp.SaTokenInfo;
import cn.dev33.satoken.stp.StpLogic;
import cn.dev33.satoken.stp.StpUtil;
import cn.hutool.core.codec.Base64Encoder;
import cn.hutool.core.date.DatePattern;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.extra.qrcode.QrCodeUtil;
import cn.hutool.extra.qrcode.QrConfig;
import cn.hutool.http.HttpUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.logisticsrepair.customEntity.UserRolePermissionEntity;
import com.logisticsrepair.entity.UserEntity;
import com.logisticsrepair.entity.UserRoleEntity;
import com.logisticsrepair.entity.WorkerEntity;
import com.logisticsrepair.mapper.RoleMapper;
import com.logisticsrepair.mapper.UserRoleMapper;
import com.logisticsrepair.service.UserService;
import com.logisticsrepair.service.WorkerService;
import com.logisticsrepair.util.RedisUtil;
import com.logisticsrepair.util.stpInterface.StpInterfaceImpl;
import com.logisticsrepair.util.responseUtil.ResponseEnum;
import com.logisticsrepair.util.responseUtil.ResponseUtil;
import com.logisticsrepair.websocket.WebSocketServer;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.interceptor.TransactionAspectSupport;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.ModelAndView;
import redis.clients.jedis.Jedis;

import java.io.ByteArrayOutputStream;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.*;

@Slf4j
@Controller
@RequestMapping("/wx")
public class LoginController {

    //========================微信登录静态常量========================
    //真实账号 -- 要服务号才可以，订阅号不行
    //public static final String appID = "";
    //public static final String appSecret = "";
    //测试账号
    public static final String USER_TOKEN = "2RDfF9hhcQPqKp";
    public static final String appID = "wx09ba05658e0b3d08";
    public static final String appSecret = "de1122447ea17c80b0eb894ab7210576";
    public static final String REDIRECT_URI = "http://smallhairs.fgimaxl2.vipnps.vip/wx/respAuth1";
    //public static final String REDIRECT_URI = "http://smallhairrs.5gzvip.91tunnel.com/wx/respAuth";

    //========================自定义静态常量========================

    public static HashMap<Integer, String> err_oauth_code = new HashMap<>();
    public static HashMap<String, String> loginType = new HashMap();
    public static Jedis redis;
    public static String ADD_WORKER_PREFIX = "add_worker:";

    static {
        //非自定义的errcode，这是微信定义的，一般就用得到这些
        err_oauth_code.put(40029, "验证码无效");
        err_oauth_code.put(40163, "验证码已使用");
        err_oauth_code.put(41008, "验证码缺失");
        err_oauth_code.put(42003, "验证码超时");
        err_oauth_code.put(42009, "设置新身份");

        loginType.put("user", "wx:loginType:user");
        loginType.put("worker", "wx:loginType:worker");

        redis = RedisUtil.getRedis();
    }


    @Autowired
    UserService userService;

    @Autowired
    WorkerService workerService;

    @Autowired
    UserRoleMapper userRoleMapper;

    @Autowired
    StpInterfaceImpl stpInterface;

    //=================================微信请求接口=================================

    /**
     * https://developers.weixin.qq.com/doc/offiaccount/Basic_Information/Access_Overview.html
     * 验证消息的确来自微信服务器 --- 【这个请求函数是 开发者服务器与微信服务器 的认证配置。】
     * 开发者提交信息后，微信服务器将发送GET请求到填写的服务器地址URL上，GET请求携带参数如下表所示：
     * signature	微信加密签名，signature结合了开发者填写的token参数和请求中的timestamp参数、nonce参数。
     * timestamp	时间戳
     * nonce	随机数
     * echostr	随机字符串
     *
     * @param params 微信请求参数
     * @return echostr
     */
    @RequestMapping("/interfaceVerify")
    @ResponseBody
    public String interfaceVerify(@RequestParam Map<String, String> params) throws Exception {
        if (params.isEmpty()) return null;
        //1）将token、timestamp、nonce三个参数进行字典序排序
        String[] sortArr = new String[]{USER_TOKEN, params.get("timestamp"), params.get("nonce")};
        Arrays.sort(sortArr);
        //2）将三个参数字符串拼接成一个字符串进行sha1加密
        StringBuffer sortStr = new StringBuffer();
        sortStr.append(sortArr[0]).append(sortArr[1]).append(sortArr[2]);
        String sha1 = getSha1(sortStr.toString().getBytes());
        //3）开发者获得加密后的字符串可与signature对比，标识该请求来源于微信
        //原样返回echostr参数内容，则接入生效，成为开发者成功，否则接入失败
        if (sha1.equals(params.get("signature"))) {
            return params.get("echostr");
        }
        throw new Exception("数据验证失败");
    }

    public String getSha1(byte[] input) throws NoSuchAlgorithmException {
        MessageDigest mDigest = MessageDigest.getInstance("SHA1");
        byte[] result = mDigest.digest(input);
        StringBuffer sb = new StringBuffer();
        for (int i = 0; i < result.length; i++) {
            sb.append(Integer.toString((result[i] & 0xff) + 0x100, 16).substring(1));
        }
        return sb.toString();
    }

    //=================================开发请求=================================

    /**
     * 登录第1步。
     * 获取微信登录二维码
     *
     * @param socketID socketID
     * @return base64/jpg
     */
    public static String wxLoginQRCode(String socketID) throws UnsupportedEncodingException {
        String encode = URLEncoder.encode(REDIRECT_URI, "UTF-8");
        String wxURL = "https://open.weixin.qq.com/connect/oauth2/authorize?" +
                "appid=" + appID +
                "&redirect_uri=" + encode +
                "&response_type=code" +
                "&scope=snsapi_userinfo" +
                "&state=" + socketID +
                "#wechat_redirect";
        log.info(wxURL);
        QrConfig qrConfig = new QrConfig(300, 300);
        qrConfig.setMargin(0).setImg("static/images/wechat.png");

        ByteArrayOutputStream os = new ByteArrayOutputStream();
        QrCodeUtil.generate(wxURL, qrConfig, "JPG", os);

        String baseImg = "data:image/jpg;base64," + Base64Encoder.encode(os.toByteArray());
        return baseImg;
    }

    /**
     * 登录第2步。
     * 微信服务器回调地址（扫码后微信跳转地址）[核心]
     * <p>
     * 流程：
     * 1、获取到 [code, state(socketID)]，根据code 获取[accessToken, openid]
     * 2、获取到 [accessToken, openid]，据此 获取[userInfo]
     * 3、根据 [openid] redis查询 用户存在和类型
     * 3.1、不存在：根据[state]为key、[accessToken, userInfo]为value，缓存3分钟
     * 3.2、存在：返回[REDIRECT_LOGIN(43011)]登录重定向，根据[state]为key、[openid]为value，缓存1分钟，客户端自动发起Http请求登录
     *
     * @param map [code, socketID]
     * @return
     */
    @Deprecated
    @RequestMapping("/respAuth")
    public ModelAndView respAuth(@RequestParam Map<String, String> map) {
        //提取code state
        String code = map.get("code"), state = map.get("state");

        //回调成功，返回用户提示：扫码成功
        WebSocketServer.sendMessage(ResponseUtil.setE(ResponseEnum.OK).setMsg("扫码成功").toJSONString(), state);

        //根据开发者信息+code获取访问令牌access_token和openid
        String url = "https://api.weixin.qq.com/sns/oauth2/access_token?" +
                "appid=" + appID +
                "&secret=" + appSecret +
                "&code=" + code +
                "&grant_type=authorization_code";
        String getAccessToken = HttpUtil.post(url, "");
        JSONObject jsonAccessToken = JSONObject.parseObject(getAccessToken);
        String openid = jsonAccessToken.getString("openid");
        Object errcode = jsonAccessToken.get("errcode");

        //用户手机微信扫码后回调页面
        ModelAndView modelAndView = new ModelAndView("loginStatus");

        //校验验证码过期有效性
        if (err_oauth_code.containsKey(errcode)) {
            modelAndView.addObject("errcode", err_oauth_code.get(errcode));
            return modelAndView;
        }

        //根据令牌access_token和用户openid获取用户信息

        String userInfo = "https://api.weixin.qq.com/sns/userinfo?" +
                "access_token=" + jsonAccessToken.getString("access_token") +
                "&openid=OPENID" + openid +
                "&lang=zh_CN";
        String getUserInfo = HttpUtil.post(userInfo, "");
        JSONObject jsonUserInfo = JSONObject.parseObject(getUserInfo);

        //判断为新用户（调用方法isNewUser()），回调页面提示：设置新用户
        String isNullOrUserType = queryUserLoginType(openid);

        //是新用户
        if (StrUtil.isEmpty(isNullOrUserType)) {
            HashMap<Object, Object> hashMap = new HashMap<>();
            hashMap.put("accessToken", jsonAccessToken);
            hashMap.put("userInfo", jsonUserInfo);
            redis.setex(state, 60 * 3, JSON.toJSONString(hashMap));
            WebSocketServer.sendMessage(ResponseUtil.setE(ResponseEnum.USER_NO_EXISTS).toJSONString(), state);
            modelAndView.addObject("errcode", err_oauth_code.get(42009).toString());
            return modelAndView;
        }

        HashMap saToken = login(openid, isNullOrUserType, jsonUserInfo);

        WebSocketServer.sendMessage(ResponseUtil.setE(ResponseEnum.LOGIN_SUCCESS).setData(saToken).toJSONString(), state);
        modelAndView.addObject("errcode", 0);
        return modelAndView;
    }

    @RequestMapping("/respAuth1")
    public ModelAndView respAuth1(@RequestParam Map<String, String> map) {
        //提取code state
        String code = map.get("code"), state = map.get("state");

        //回调成功，返回用户提示：扫码成功
        WebSocketServer.sendMessage(ResponseUtil.setE(ResponseEnum.OK).setMsg("扫码成功").toJSONString(), state);

        //根据开发者信息+code获取访问令牌access_token和openid
        String url = "https://api.weixin.qq.com/sns/oauth2/access_token?" +
                "appid=" + appID +
                "&secret=" + appSecret +
                "&code=" + code +
                "&grant_type=authorization_code";
        String getAccessToken = HttpUtil.post(url, "");
        JSONObject jsonAccessToken = JSONObject.parseObject(getAccessToken);
        String openid = jsonAccessToken.getString("openid");
        Object errcode = jsonAccessToken.get("errcode");

        //用户手机微信扫码后回调页面
        ModelAndView modelAndView = new ModelAndView("loginStatus");

        //校验验证码过期有效性
        if (err_oauth_code.containsKey(errcode)) {
            modelAndView.addObject("errcode", err_oauth_code.get(errcode));
            return modelAndView;
        }

        //根据令牌access_token和用户openid获取用户信息

        String userInfo = "https://api.weixin.qq.com/sns/userinfo?" +
                "access_token=" + jsonAccessToken.getString("access_token") +
                "&openid=OPENID" + openid +
                "&lang=zh_CN";
        String getUserInfo = HttpUtil.post(userInfo, "");
        JSONObject jsonUserInfo = JSONObject.parseObject(getUserInfo);

        // 判断为新用户（调用方法isNewUser()），回调页面提示：设置新用户
        UserRolePermissionEntity queryUserType = userRoleMapper.queryUserRoleByUserId(openid);

        //是新用户
        if (ObjectUtil.isEmpty(queryUserType)) {
            HashMap<Object, Object> hashMap = new HashMap<>();
            hashMap.put("accessToken", jsonAccessToken);
            hashMap.put("userInfo", jsonUserInfo);
            redis.setex(state, 60 * 3, JSON.toJSONString(hashMap));
            WebSocketServer.sendMessage(ResponseUtil.setE(ResponseEnum.USER_NO_EXISTS).toJSONString(), state);
            modelAndView.addObject("errcode", err_oauth_code.get(42009).toString());
            return modelAndView;
        }

        HashMap saToken = login(openid, queryUserType.getUserType(), jsonUserInfo);

        WebSocketServer.sendMessage(ResponseUtil.setE(ResponseEnum.LOGIN_SUCCESS).setData(saToken).toJSONString(), state);
        modelAndView.addObject("errcode", 0);
        return modelAndView;
    }

    /**
     * 根据openid查找用户类型（等同查找用户存在）
     * <p>
     * 获取用户类型 == 判断是否为新用户
     * 不存在：获取类型失败 == 新用户
     * 存在：获取类型成功 == 注册用户
     *
     * @param openid openid
     * @return [user, worker] 或 null
     */
    @Deprecated
    public static String queryUserLoginType(String openid) {
        Jedis redis = RedisUtil.getRedis();
        redis.select(1);

        Set<String> keySet = loginType.keySet();
        for (String key : keySet) {
            String cachePath = loginType.get(key);
            long size = redis.llen(cachePath);
            for (int i = 0; i < Math.round((float) size / 2); i++) {
                if (redis.lindex(cachePath, i).equals(openid)) {
                    return key;
                }
                if (redis.lindex(cachePath, size - i - 1).equals(openid)) {
                    return key;
                }
            }
        }
        return null;
    }

    @Autowired
    RoleMapper roleMapper;

    @GetMapping("/getUserRole")
    @ResponseBody
    public ResponseUtil getUserRole() {
        return ResponseUtil.setE(ResponseEnum.OK).setData(roleMapper.queryRoleIdAndRoleName());
    }


    @Transactional
    @PostMapping("/newUserRegister")
    @ResponseBody
    public ResponseUtil newUserRegister(@RequestBody Map<String, String> data) {
        // {code: '43007', userType: 'user', state: '1652505341279', invitationCode: '', id: '1524642948199759874'}
        log.info("==>新用户注册信息: {}", JSON.toJSONString(data));
        if (!data.get("code").equals(StrUtil.toString(ResponseEnum.USER_NO_EXISTS.getCode())) &&
                !loginType.containsKey(data.get("userType")) &&
                !redis.exists(data.get("state")))
            return ResponseUtil.setE(ResponseEnum.PARAMS_ERROR).setMsg("注册登录信息错误");

        /**
         * 若上面验证没有问题，则下面需要
         * 1、根据[state]获取缓存在的数据，保存用户类型[openid, userType]
         * 2、登录[openid, loginType] 并 接收[tokenName, tokenValue]
         * 3、返回特定 [状态码] 让用户自动关闭socket连接
         * 4、响应返回 登录成功 [tokenName, tokenValue]
         */

        JSONObject jsonState = JSONObject.parseObject(redis.get(data.get("state")));
        JSONObject jsonUserInfo = JSONObject.parseObject(jsonState.getString("userInfo"));
        String openid = jsonUserInfo.getString("openid");

        try {
            switch (data.get("userType")) {
                case "user":
                    UserEntity userInfoEntity = new UserEntity();
                    userInfoEntity.setOpenid(openid);
                    userInfoEntity.setNickname(jsonUserInfo.getString("nickname"));
                    userInfoEntity.setHeadimgurl(jsonUserInfo.getString("headimgurl"));
                    userInfoEntity.setRegisterTime(new Date());
                    userService.save(userInfoEntity);
                    break;
                case "worker":
                    WorkerEntity workerInfoEntity = new WorkerEntity();
                    workerInfoEntity.setOpenid(openid);
                    workerInfoEntity.setNickname(jsonUserInfo.getString("nickname"));
                    workerInfoEntity.setHeadimgurl(jsonUserInfo.getString("headimgurl"));
                    String invitationCode = ADD_WORKER_PREFIX + data.get("invitationCode");
                    redis.select(0);
                    if (!redis.exists(invitationCode)) {
                        return ResponseUtil.setE(ResponseEnum.PARAMS_ERROR).setMsg("邀请码无效");
                    }
                    Map<String, String> map = JSONObject.parseObject(redis.get(invitationCode), Map.class);
                    workerInfoEntity.setWorkerName(map.get("workerName"));
                    workerInfoEntity.setWorkerPhone(map.get("workerPhone"));
                    workerInfoEntity.setWorkerEmail(map.get("workerEmail"));
                    workerInfoEntity.setRegisterTime(DateUtil.format(new Date(), DatePattern.NORM_DATETIME_PATTERN));
                    workerService.save(workerInfoEntity);
                    redis.del(invitationCode);
                    break;
                default:
                    break;
            }

            String roleId = data.get("id");
            UserRoleEntity userRoleEntity = new UserRoleEntity();
            userRoleEntity.setUserId(openid);
            userRoleEntity.setRoleId(roleId);
            userRoleMapper.insert(userRoleEntity);
        } catch (Exception e) {
            TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
            e.printStackTrace();
        }


        HashMap saToken = login(openid, data.get("userType"), jsonUserInfo);
        return ResponseUtil.setE(ResponseEnum.REGISTER_SUCCESS).setData(saToken);
    }


    /**
     * 登录触发器
     *
     * @param openid    用户ID
     * @param loginType 登录类型
     * @param userInfo  用户基本信息
     * @return tokenInfo
     */
    public HashMap login(String openid, String loginType, JSONObject userInfo) {
        StpUtil.setStpLogic(new StpLogic(loginType));
        StpUtil.login(openid);
        stpInterface.getRoleList(openid, loginType);

        System.out.println("\033[32;4m" + "++++++++++++++++++++++++++++++" + "\033[0m");
        System.out.println("\033[32;4m" + "===>当前账号" + openid + " 登录类型：" + StpUtil.getLoginType() + "\033[0m");
        System.out.println("\033[32;4m" + "===>当前账号的角色集合：" + StpUtil.getRoleList() + "\033[0m");
        System.out.println("\033[32;4m" + "===>当前账号的权限码集合：" + StpUtil.getPermissionList() + "\033[0m");
        System.out.println("\033[32;4m" + "++++++++++++++++++++++++++++++" + "\033[0m");

        HashMap<String, Object> hashMap = new HashMap<>();
        SaTokenInfo tokenInfo = StpUtil.getTokenInfo();

        hashMap.put(tokenInfo.getTokenName(), tokenInfo.getTokenValue());
        hashMap.put("loginType", tokenInfo.getLoginType());
        hashMap.put("nickname", userInfo.getString("nickname"));
        hashMap.put("headimgurl", userInfo.getString("headimgurl"));

        return hashMap;
    }
}
